/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package deatax;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;

/**
 *
 * @author nrechten
 */
public class Map {

    private GameData data;
    private int width;
    private int height;
    private Tile[][] map;
    private List<Tile> tiles = new ArrayList<Tile>();
    private ArrayList<Region> regions = new ArrayList<Region>();
    private Landmass landmass;
    private List<Highlight> highlights = new ArrayList<Highlight>();
    private Tile selectedTile = null;

    public Map(Landmass landmass, GameData data, int width, int height) {
        this.landmass = landmass;
        this.width = width;
        this.height = height;
        this.data = data;
        clear();
    }

    public void clear() {
        map = new Tile[getWidth()][getHeight()];
    }

    public void seed(int regions, int region_size) {
        int sx = width / 2;
        int sy = height / 2;

        for (int i = 0; i < regions; i++) {
            int[] ia = newRegion(sx, sy, region_size, i + 1);
            if (ia == null) {
                return;
            }
            sx = ia[0];
            sy = ia[1];
        }
        createOceanTiles();
        populateTiles();
    }

    public int[] newRegion(int x, int y, int size, int id) {

        Region r = new Region();

        getRegions().add(r);

        Tile t = new Tile(x, y, r);
        t.setTerrain(landmass.generateTerrain());
        map[x][y] = t;
        tiles.add(t);
        r.getTiles().add(t);

        int[] next;
        for (int i = 0; i < size; i++) {
            next = skirt(x, y);
            while (i < size && next != null) {
                t = new Tile(next[0], next[1], r);
                t.setTerrain(landmass.generateTerrain());
                r.getTiles().add(t);
                tiles.add(t);
                map[next[0]][next[1]] = t;
                if (prob(next[0], next[1], r) > 0.6) {
                    x = next[0];
                    y = next[1];
                }
                next = skirt(x, y);
                i++;
            }
            t = r.getTiles().get(RNG.rand(r.getTiles().size()));
            x = t.x;
            y = t.y;
        }

        int[] ra = randomArray(getRegions().size());
        for (int q = 0; q < ra.length; q++) {
            Region nr = getRegions().get(ra[q]);
            int[] ia = randomArray(nr.getTiles().size());
            for (int i = 0; i < ia.length; i++) {
                int j = ia[i];
                next = skirt(nr.getTiles().get(j).x, nr.getTiles().get(j).y);
                if (next != null) {
                    return next;
                }
            }
        }
        return null;
    }

    /**
     * returns [count] random numbers between 0 and [count - 1]
     * @param count Number of random numbers to generate
     * @return
     */
    public int[] randomArray(int count) {
        return randomArray(count, 0, count);
    }

    public Tile getSelectedTile() {
        return selectedTile;
    }

    public void setSelectedTile(Tile selectedTile) {
        //System.out.println("foo:"+selectedTile);
        this.selectedTile = selectedTile;
    }

    public int[] randomArray(int count, int min, int max) {

        LinkedList<Integer> bag = new LinkedList<Integer>();
        for (int i = min; i < max; ++i) {
            bag.add(i);
        }
        int[] val = new int[count];
        int i = 0;
        while (!bag.isEmpty()) {
            val[i++] = bag.remove(RNG.rand(bag.size()));
        }
        return val;
    }
//    public static int[] OFFEX = new int[]{-1, 0, 1, -1, 0, 1};
//    public static int[] OFFEY = new int[]{-1, -1, -1, 0, 1, 0};
//    public static int[] OFFOX = new int[]{-1, 0, 1, -1, 0, 1};
//    public static int[] OFFOY = new int[]{0, -1, 0, 1, 1, 1};
    public static int[] OFFEX = new int[]{1, 0, -1, -1, 0, 1};
    public static int[] OFFEY = new int[]{-1, -1, -1, 0, 1, 0};
    public static int[] OFFOX = new int[]{1, 0, -1, -1, 0, 1};
    public static int[] OFFOY = new int[]{0, -1, 0, 1, 1, 1};

    public int[] skirt(int x, int y) {
        int offset = RNG.rand(6);
        int[] OFFX = OFFOX;
        int[] OFFY = OFFOY;
        if (x % 2 == 0) {
            OFFX = OFFEX;
            OFFY = OFFEY;
        }

        for (int i = 0; i < 6; i++) {

            if (map[OFFX[(i + offset) % 6] + x][OFFY[(i + offset) % 6] + y] == null) {
                return new int[]{OFFX[(i + offset) % 6] + x, OFFY[(i + offset) % 6] + y};
            }
        }

        return null;
    }

    public double prob(int x, int y, Region r) {

        if (atlimits(x, y)) {
            return 0;
        }

        int prob = 0;
        int[] OFFX = OFFOX;
        int[] OFFY = OFFOY;
        if (x % 2 == 0) {
            OFFX = OFFEX;
            OFFY = OFFEY;
        }
        for (int i = 0; i < 6; i++) {
            Tile tile = map[OFFX[i] + x][OFFY[i] + y];
            if (tile != null && tile.partOf(r)) {
                prob++;
            }
        }

//        System.out.println(prob / 8f * Math.random());

        return prob / (RNG.rand(6) + 1);
    }

    public boolean atlimits(int x, int y) {
        if (x <= 1 || x >= getWidth() - 1 || y <= 1 || y >= getHeight() - 1) {
            return true;
        }
        return false;
    }

    /**
     * @return the width
     */
    public int getWidth() {
        return width;
    }

    /**
     * @param width the width to set
     */
    public void setWidth(int width) {
        this.width = width;
    }

    /**
     * @return the height
     */
    public int getHeight() {
        return height;
    }

    /**
     * @param height the height to set
     */
    public void setHeight(int height) {
        this.height = height;
    }

    /**
     * @return the cache
     */
    public Tile[][] getMap() {
        return map;
    }

    public Tile getRandomTile() {
        return tiles.get(RNG.rand(tiles.size()));
    }

    public Tile getTile(int x, int y) {
        if (x < 0 || x >= map.length || y < 0 || y >= map[0].length) {
            return null;
        }
        return map[x][y];
    }

    /**
     * @return the regions
     */
    public ArrayList<Region> getRegions() {
        return regions;
    }

    /**
     * @param regions the regions to set
     */
    public void setRegions(ArrayList<Region> regions) {
        this.regions = regions;
    }

    protected void createOceanTiles() {
        Region ocean_region = new Region();
        getRegions().add(ocean_region);

        ArrayList<Tile> o_tiles = new ArrayList<Tile>();
        for (Tile tile : tiles) {
            int[] OFFX = OFFOX;
            int[] OFFY = OFFOY;
            if (tile.x % 2 == 0) {
                OFFX = OFFEX;
                OFFY = OFFEY;
            }
            for (int i = 0; i < 6; i++) {
                if (map[OFFX[i] + tile.x][OFFY[i] + tile.y] == null) {
                    Tile t = new Tile(OFFX[i] + tile.x, OFFY[i] + tile.y, ocean_region);
                    t.setTerrain(data.getGame().getTerrain("Ocean"));
                    map[OFFX[i] + tile.x][OFFY[i] + tile.y] = t;
                    ocean_region.getTiles().add(t);
                    o_tiles.add(t);
                }
            }
        }
        tiles.addAll(o_tiles);
    }

    protected void populateTiles() {

        for (Tile tile : tiles) {
            int[] OFFX = OFFOX;
            int[] OFFY = OFFOY;
            if (tile.x % 2 == 0) {
                OFFX = OFFEX;
                OFFY = OFFEY;
            }
            for (int i = 0; i < 6; i++) {
                if (map[OFFX[i] + tile.x][OFFY[i] + tile.y] != null) {
                    tile.addNeigbour(map[OFFX[i] + tile.x][OFFY[i] + tile.y], i);
                }
            }
        }
    }

    public ArrayList<PathInfo> findPath(Tile startpoint, ArrayList<Tile> endpoints, int max_dist, boolean cost_care, boolean is_water, Player context) {
        return findPath(startpoint, endpoints, max_dist, cost_care, is_water, context, null, null, null, null, true, null, null, true);
    }
    /**
     * Finds a path
     * @param startpoint Tile to search from
     * @param t2 Tile to search toward
     * @param max_dist Maximum distance to search
     * @param terrain_allow a list of terrain types that can be traversed
     * @param terrain_restrict a list of terrain types to not allow
     * @param improve_allow a list of improvement types that can be traversed
     * @param improve_restrict a list of improvement types to not allow
     * @param player_allow a list of tile owners that can be traversed
     * @param player_restrict a list of tile owners to not allow
     * @return 
     */
    public ArrayList<PathInfo> findPath(Tile startpoint, ArrayList<Tile> endpoints, int max_dist, boolean cost_care, boolean is_water,
            Player context, ArrayList<Terrain> terrain_allow, ArrayList<Terrain> terrain_restrict,
            ArrayList<Improvement> improve_allow, ArrayList<Improvement> improve_restrict, boolean allow_unimproved,
            ArrayList<Player> player_allow, ArrayList<Player> player_restrict, boolean allow_unowned) {

        boolean has_path = false;

        if (startpoint == null || endpoints == null) {
            return null;
        }

        if (terrain_allow == null) {
            terrain_allow = data.getGame().getAllTerrains();
        }
        if (terrain_restrict == null) {
            terrain_restrict = new ArrayList<Terrain>();
        }
        if (improve_allow == null) {
            improve_allow = data.getGame().getAllImproves();
        }
        if (improve_restrict == null) {
            improve_restrict = new ArrayList<Improvement>();
        }
        if (player_allow == null) {
            player_allow = data.getRoster().getPlayers();
        }
        if (player_restrict == null) {
            player_restrict = new ArrayList<Player>();
        }

        PathInfo prev_path = null;

        HashMap<Tile, PathInfo> path_info = new HashMap<Tile, PathInfo>();

        path_info.put(startpoint, new PathInfo(startpoint, null, 0));

        while (!has_path) {
            int min_cost = 9999;
            Tile tile = null;
            for (Entry<Tile, PathInfo> e : path_info.entrySet()) {
                PathInfo p = e.getValue();
                if (!p.isLocked() && p.getCost() < min_cost) {
                    prev_path = p;
                    tile = e.getKey();
                    min_cost = p.getCost();
                }
            }

            if (tile == null || min_cost > max_dist) {
                return null;
            } else if (endpoints.contains(tile)) {
                has_path = true;
            } else {
                prev_path.setLocked(true);

                for (Tile t : tile.getNeighbours()) {
                    if (t != null) {
                        if (!path_info.containsKey(t) || !path_info.get(t).isLocked()) {
                            Terrain ter = t.getTerrain();
                            if (terrain_allow.contains(ter) && !terrain_restrict.contains(ter)) {
                                Improvement imp = t.getImprovement();
                                if ((imp == null && allow_unimproved) || (improve_allow.contains(imp) && !improve_restrict.contains(imp))) {
                                    Player own = t.getOwner();
                                    if ((own == null && allow_unowned) || (player_allow.contains(own) && !player_restrict.contains(own))) {
                                        //All conditions met
                                        int best_cost = 9999;
                                        if (path_info.get(t) != null) {
                                            best_cost = path_info.get(t).getCost();
                                        }
                                        int newCost = min_cost + (cost_care ? t.getMoveCost(context, is_water) : 1);
                                        if (newCost < best_cost) {
                                            PathInfo pi = new PathInfo(t, prev_path, newCost);
                                            path_info.put(t, pi);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        ArrayList<PathInfo> result = new ArrayList<PathInfo>();
        PathInfo fpath = prev_path;

        while (fpath.getTile() != startpoint) {
            result.add(fpath);
            fpath = fpath.getPrev();
        }

        return result;
    }

    public ArrayList<PathInfo> getRange(Tile startpoint, int max_dist, boolean cost_care, boolean is_water, Player context) {
        return getRange(startpoint, max_dist, cost_care, is_water, context, null, null, null, null, true, null, null, true);
    }
    
    public ArrayList<PathInfo> getRange(Tile startpoint, int max_dist, boolean cost_care, boolean is_water,
            Player context, ArrayList<Terrain> terrain_allow, ArrayList<Terrain> terrain_restrict,
            ArrayList<Improvement> improve_allow, ArrayList<Improvement> improve_restrict, boolean allow_unimproved,
            ArrayList<Player> player_allow, ArrayList<Player> player_restrict, boolean allow_unowned) {

        boolean finished = false;

        if (startpoint == null) {
            return null;
        }

        if (terrain_allow == null) {
            terrain_allow = data.getGame().getAllTerrains();
        }
        if (terrain_restrict == null) {
            terrain_restrict = new ArrayList<Terrain>();
        }
        if (improve_allow == null) {
            improve_allow = data.getGame().getAllImproves();
        }
        if (improve_restrict == null) {
            improve_restrict = new ArrayList<Improvement>();
        }
        if (player_allow == null) {
            player_allow = data.getRoster().getPlayers();
        }
        if (player_restrict == null) {
            player_restrict = new ArrayList<Player>();
        }

        PathInfo prev_path = null;

        //<PathInfo> path_info = new LinkedList<PathInfo>();
        HashMap<Tile, PathInfo> path_info = new HashMap<Tile, PathInfo>();

        path_info.put(startpoint, new PathInfo(startpoint, null, 0));

        while (!finished) {
            int min_cost = 9999;
            Tile tile = null;
            for (Entry<Tile, PathInfo> e : path_info.entrySet()) {
                PathInfo p = e.getValue();
                if (!p.isLocked() && p.getCost() < min_cost) {
                    prev_path = p;
                    tile = e.getKey();
                    min_cost = p.getCost();
                }
            }

            if (tile == null || min_cost > max_dist) {
                finished = true;
            } else {
                //System.out.println("pi=" + tile.getX() + " " + tile.getY());
                prev_path.setLocked(true);

                for (Tile t : tile.getNeighbours()) {
                    if (t != null) {
                        if (!path_info.containsKey(t) || !path_info.get(t).isLocked()) {
                            //Check all the allows and restricts
                            Terrain ter = t.getTerrain();
                            if (terrain_allow.contains(ter) && !terrain_restrict.contains(ter)) {
                                Improvement imp = t.getImprovement();
                                if ((imp == null && allow_unimproved) || (improve_allow.contains(imp) && !improve_restrict.contains(imp))) {
                                    Player own = t.getOwner();
                                    if ((own == null && allow_unowned) || (player_allow.contains(own) && !player_restrict.contains(own))) {
                                        //All conditions met
                                        int best_cost = 9999;
                                        if (path_info.get(t) != null) {
                                            best_cost = path_info.get(t).getCost();
                                        }
                                        int newCost = min_cost + (cost_care ? (t.getMoveCost(context, is_water)) : 1);
                                        if (newCost < best_cost) {
                                            PathInfo pi = new PathInfo(t, prev_path, newCost);
                                            path_info.put(t, pi);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        ArrayList<PathInfo> result = new ArrayList<PathInfo>();
        for (Entry<Tile, PathInfo> e : path_info.entrySet()) {
            if (e.getValue().isLocked()) {
                result.add(e.getValue());
            }
        }

        return result;
    }

    /**
     * @return the highlights
     */
    public List<Highlight> getHighlights() {
        return highlights;
    }

    public void clearHighlights() {
        highlights.clear();
    }

    public void addHighlight(Highlight highlight) {
        highlights.add(highlight);
    }

    public void removeHighlight(Highlight highlight) {
        highlights.remove(highlight);
    }
}
